package com.jeasonzhao.model;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import org.apache.commons.beanutils.PropertyUtils;
import com.jeasonzhao.commons.basic.StringCollection;
import com.jeasonzhao.commons.utils.Algorithms;
import com.jeasonzhao.commons.utils.DataTypes;
import com.jeasonzhao.commons.xml.XMLNode;

public class MappedFieldItem implements java.io.Serializable
{
    private static final long serialVersionUID = 1L;
    private MappedInformation model = null;
    private Field field = null;
    private String name = null;
    private StringCollection alias = null;
    private Class<?> componentClass = null;
    private Class<?> ownerClass = null;
    private Class<?> propertyType = null;
//    private Method getter=null;
//    private Method setter=null;

    private boolean nullable = true;

    private boolean cdata = false;
    private boolean newNode = false;
    private boolean primaryKey = false;
    private FieldAppendix appendix = null;
    private MappedFieldItem()
    {

    }

    public MappedFieldItem(MappedInformation mapInfo,Field field)
        throws ModelException
    {
        this.ownerClass = null != field ? field.getDeclaringClass() : (null != mapInfo ? mapInfo.getOriginalClass() : null);
        if(null == field || null == mapInfo)
        {
            throw new ModelConfigurationException("Can not construct [" + (null == ownerClass ?
                "NULL" : ownerClass.getName()) + "] a item from a null pointer.");
        }
        this.model = mapInfo;
        this.field = field;
        this.propertyType = null;
        normalize();
    }

    public static MappedFieldItem pojoField(MappedInformation mapInfo
                                            ,String fieldName,Class<?> fieldType)
        throws ModelException
    {
        if(null == mapInfo || null == fieldName || null == fieldType)
        {
            throw new ModelConfigurationException("Can not construct a item from a null pointer.");
        }
        MappedFieldItem m = new MappedFieldItem();
        m.ownerClass = mapInfo.getOriginalClass();
        m.model = mapInfo;
        m.addName(fieldName);
        try
        {
            m.field = m.ownerClass.getDeclaredField(fieldName);
            if(null == m.field)
            {
                m.field = m.ownerClass.getField(fieldName);
            }
            if(null != m.field)
            {
                m.primaryKey = m.field.getAnnotation(javax.persistence.Id.class) != null;
            }
        }
        catch(SecurityException ex)
        {
        }
        catch(NoSuchFieldException ex)
        {
        }
        m.propertyType = fieldType;
        m.normalize();
        return m;
    }

    public MappedFieldItem addName(String name)
    {
        if(Algorithms.isEmpty(name))
        {
            return this;
        }
        name = name.trim();
        if(Algorithms.isEmpty(this.name))
        {
            this.name = name;
        }
        if(null == alias)
        {
            this.alias = new StringCollection();
            this.alias.add(name);
        }
        else
        {
            if(false == this.matchs(name))
            {
                this.alias.add(name);
            }
        }
        return this;
    }

    public FieldAppendix getAppendix()
    {
        return appendix;
    }

    public boolean isCdata()
    {
        return cdata;
    }

    public Class<?> getComponentClass()
    {
        return componentClass;
    }

    public Field getField()
    {
        return field;
    }

    public Class<?> getType()
    {
        return null == field ?
            (null == propertyType ? Object.class : propertyType)
            : (null == field ? Object.class : field.getType());
    }

    public String getName()
    {
        return name;
    }

    public boolean isNewNode()
    {
        return newNode;
    }

    public Class<?> getOwnerClass()
    {
        return ownerClass;
    }

    public boolean isPrimaryKey()
    {
        return primaryKey;
    }

    public MappedInformation getModel()
    {
        return model;
    }

    public boolean isNullable()
    {
        return nullable;
    }

    public void normalize()
    {
        if(null == this.alias)
        {
            this.alias = new StringCollection();
        }
        if(Algorithms.isEmpty(name) && null != this.alias && this.alias.size() > 0)
        {
            name = this.alias.get(0);
        }
        if(Algorithms.isEmpty(name) && null != this.field)
        {
            name = this.field.getName();
        }
        if(false == this.matchs(this.name))
        {
            alias.add(this.name.trim());
        }
        this.alias.trim().uniqueItems();
        this.appendix = new FieldAppendix(this);
        if(this.isPrimaryKey())
        {
            this.setNullable(false);
        }
//        if(null!=this.field && null== this.setter && null==this.getter)
//        {
//            String fname = this.field.getName();
//            fname = fname.toUpperCase().charAt(0) + fname.substring(1);
//            String setterName = "set" + fname;
//            String getterName = (DataTypes.isBoolean(this.field.getType()) ? "is" : "get") + fname;
//            try
//            {
//                this.setter = this.ownerClass.getMethod(setterName,this.field.getType());
//                this.getter = this.ownerClass.getMethod(getterName);
//            }
//            catch(SecurityException ex)
//            {
//                ex.printStackTrace();
//            }
//            catch(NoSuchMethodException ex)
//            {
//                ex.printStackTrace();
//            }
//        }
    }

    public boolean matchs(String strName)
    {
        if(Algorithms.isEmpty(strName))
        {
            return false;
        }
        else if(null != this.name && this.name.equalsIgnoreCase(strName.trim()))
        {
            return true;
        }
        else if(null != this.alias)
        {
            for(String s : this.alias)
            {
                if(null != s && s.equalsIgnoreCase(strName.trim()))
                {
                    return true;
                }
            }
            return false;
        }
        else
        {
            return false;
        }
    }

    public Object getValue(Object item)
        throws ModelException
    {
        if(null == item)
        {
            return null;
        }
        try
        {
            if(null == field)
            {
                return PropertyUtils.getSimpleProperty(item,this.name);
            }
            else
            {
                field.setAccessible(true);
                return field.get(item);
            }
        }
        catch(IllegalAccessException ex)
        {
            throw new ModelException("Can not get the value from " +
                                     this +":" + ex.getMessage(),ex);
        }
        catch(IllegalArgumentException ex)
        {
            throw new ModelException("Can not get the value from " +
                                     this +":" + ex.getMessage(),ex);
        }
        catch(NoSuchMethodException ex)
        {
            throw new ModelException("Can not get the value from " +
                                     this +":" + ex.getMessage(),ex);
        }
        catch(InvocationTargetException ex)
        {
            throw new ModelException("Can not get the value from " +
                                     this +":" + ex.getMessage(),ex);
        }
    }

    public MappedFieldItem setValue(Object item,Object value)
        throws ModelException
    {
        if(null == item)
        {
            return this;
        }
        try
        {
//            System.out.println(this.getType().toString()+"********************"+DataTypes.from(this.getType()));
            if(DataTypes.isNumeric(this.getType()) && Algorithms.isEmpty(value))
            {
                value = 0;
            }
            if(null == field)
            {
                PropertyUtils.setSimpleProperty(item,this.name,Algorithms.castValue(this.getType(),value));
            }
            else
            {
                field.setAccessible(true);
                field.set(item,Algorithms.castValue(this.getType(),value));
            }
            return this;
        }
        catch(IllegalAccessException ex)
        {
            String info = this.getOwnerClass().getName() + ">" + this.getName()
                + " [" + value + "]";
            throw new ModelException("Can not set the value to " +
                                     info + ":" + ex.getMessage(),ex);

        }
        catch(IllegalArgumentException ex)
        {
            String info = this.getOwnerClass().getName() + ">" + this.getName()
                + " [" + value + "]";
            throw new ModelException("Can not set the value to " +
                                     info + ":" + ex.getMessage(),ex);

        }
        catch(NoSuchMethodException ex)
        {
            String info = this.getOwnerClass().getName() + ">" + this.getName()
                + " [" + value + "]";
            throw new ModelException("Can not set the value to " +
                                     info + ":" + ex.getMessage(),ex);
        }
        catch(InvocationTargetException ex)
        {
            String info = this.getOwnerClass().getName() + ">" + this.getName()
                + " [" + value + "]";
            throw new ModelException("Can not set the value to " +
                                     info + ":" + ex.getMessage(),ex);
        }
    }

    public boolean isDictionaryNode()
    {
        return null == this.appendix ? false : this.appendix.isDictionaryNode();
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public void setPrimaryKey(boolean primaryKey)
    {
        this.primaryKey = primaryKey;
    }

    public void setComponentClass(Class<?> componentClass)
    {
        this.componentClass = componentClass;
    }

    public void setCdata(boolean cdata)
    {
        this.cdata = cdata;
    }

    public void setNewNode(boolean newNode)
    {
        this.newNode = newNode;
    }

    public void setNullable(boolean nullable)
    {
        this.nullable = nullable;
    }

    public boolean isXMLNode()
    {
        return XMLNode.class.isAssignableFrom(this.getType());
    }

    public boolean isCollection()
    {
        return null != this.getComponentClass() && DataTypes.isArray(this.getType());
    }

    public String[] getNames()
    {
        if(null == alias)
        {
            return new String[]
                {};
        }
        else
        {
            String[] ary = new String[alias.size()];
            alias.toArray(ary);
            return ary;
        }
    }

    public String getCollectionName()
    {
        String strName = this.name;
        if(strName.indexOf(".") > 0)
        {
            strName = strName.substring(0,strName.indexOf("."));
        }
        return strName;
    }

    public boolean isEnumName()
    {
        return this.getType().isEnum() && (null == this.appendix ? true : this.appendix.isEnumName());
    }
    public String toString()
    {
        return "" + this.getOwnerClass().getName() + ">" + this.getName();
    }
}
